﻿/******************************************
 * 
 * @author Рамиль Алиякберов a.k.a R@Me0
 * http://www.rame0.ru
 * 
 * Data Provider для API ВКонтакте.
 * 
 * Реализует асинхронные запросы к функциям API из приложений на Silverlight
 * 
 * License:
 * Read License.txt
 */


using System;
using System.Windows.Browser;
using System.Windows.Threading;
using System.Collections.Generic;
using System.Xml.Linq;

namespace VKontakte
{
    /// <summary>
    /// Параметры запуска приложения
    /// </summary>
    public class StartInfo
    {
        /// <summary>
        /// это адрес сервиса API, по которому необходимо осуществлять запросы
        /// </summary>
        public string api_url;

        /// <summary>
        /// это id запущенного приложения
        /// </summary>
        public string api_id;
        /// <summary>
        /// Настройки приложения
        /// </summary>
        public /*APIConnection.userSettings*/string api_settings;
        /// <summary>
        ///  это id пользователя, который просматривает приложение
        /// </summary>
        public string viewer_id;
        /// <summary>
        /// это тип пользователя, который просматривает приложение
        /// </summary>
        public /*APIConnection.ViewerType*/ int viewer_type;
        /// <summary>
        /// это id пользователя, со страницы которого было запущено приложение. 
        /// Если приложение запущено не со страницы пользователя, то значение равно 0
        /// </summary>
        public string user_id;
        /// <summary>
        /// это id группы, со страницы которой было запущено приложение. 
        /// Если приложение запущено не со страницы группы, то значение равно 0
        /// </summary>
        public string group_id;
        /// <summary>
        /// если пользователь установил приложение – true, иначе – false
        /// </summary>
        public bool is_app_user;
        /// <summary>
        /// это ключ, необходимый для авторизации пользователя на стороннем сервере
        /// </summary>
        public string auth_key;
        /// <summary>
        /// Язык пользователя
        /// </summary>
        public /*APIConnection.LanguageType*/ int language;
        /// <summary>
        /// Секрет, необходимый для осуществления подписи запросов к API
        /// </summary>
        public string secret;
        /// <summary>
        /// id сессии для осуществления запросов к API
        /// </summary>
        public string sid;
        /// <summary>
        /// Секрет
        /// </summary>
        public string server_secret;
        /// <summary>
        /// Тестоывй режим
        /// </summary>
        public bool test_mode=false;
    }

    public partial class APIConnection: ISocial
    {
        #region Класс разбора настроек пользователя
        public class userSettings
        {
            private Int32 _value;
            /// <summary>
            /// Текущее значение битовой маски настроек пользователя
            /// </summary>
            public Int32 Value
            {
                get { return _value; }
            }
            /// <summary>
            /// Пользователь разрешил отправлять ему уведомления.
            /// </summary>
            public Boolean allowNotifys
            {
                get { return (_value & 1) == 1; }
            }
            /// <summary>
            /// Доступ к друзьям.
            /// </summary>
            public Boolean accessToFriends
            {
                get { return (_value & 2) == 2; }
            }
            /// <summary>
            /// Доступ к фотографиям.
            /// </summary>
            public Boolean accessToPhotos
            {
                get { return (_value & 4) == 4; }
            }
            /// <summary>
            /// Доступ к аудиозаписям.
            /// </summary>
            public Boolean accessToAudio
            {
                get { return (_value & 8) == 8; }
            }
            /// <summary>
            /// Доступ к видеозаписям.
            /// </summary>
            public Boolean accessToVideo
            {
                get { return (_value & 16) == 16; }
            }
            /// <summary>
            /// Доступ к предложениям.
            /// </summary>
            public Boolean accessToApps
            {
                get { return (_value & 32) == 32; }
            }
            /// <summary>
            /// Доступ к вопросам.
            /// </summary>
            public Boolean accessToQuestions
            {
                get { return (_value & 64) == 64; }
            }
            /// <summary>
            /// Доступ к wiki-страницам.
            /// </summary>
            public Boolean accessToWiki
            {
                get { return (_value & 128) == 128; }
            }
            /// <summary>
            /// Добавление ссылки на приложение в меню слева.
            /// </summary>
            public Boolean allowAddLinkToMenu
            {
                get { return (_value & 256) == 256; }
            }
            /// <summary>
            /// Добавление ссылки на приложение для быстрой публикации на стенах пользователей.
            /// </summary>
            public Boolean allowAddLinkToWall
            {
                get { return (_value & 512) == 512; }
            }
            /// <summary>
            /// Доступ к статусам пользователя.
            /// </summary>
            public Boolean accessToStatus
            {
                get { return (_value & 1024) == 1024; }
            }
            /// <summary>
            /// Доступ заметкам пользователя.
            /// </summary>
            public Boolean accessToNotes
            {
                get { return (_value & 2048) == 2048; }
            }
            /// <summary>
            /// (для Desktop-приложений) Доступ к расширенным методам работы с сообщениями.
            /// </summary>
            public Boolean accessToAdvancedMessages
            {
                get { return (_value & 4096) == 4096; }
            }
            /// <summary>
            /// (для Desktop-приложений) Доступ к расширенным методам работы со стеной.
            /// </summary>
            public Boolean accessToAdvancedWall
            {
                get { return (_value & 8192) == 8192; }
            }

            /// <summary>
            /// Конструктор класса содержащего настройки пользователя
            /// </summary>
            /// <param name="userSettings">Битовая маска настроек, полученная от API</param>
            public userSettings(Int32 userSettings)
            {
                this._value = userSettings;
            }
        }
        #endregion


        StartInfo startInfo;
        DataProvider dataProvider;
        string lastError = null;
        Profile profile = null;
        bool isProfileLoaded;


        public APIConnection(string secret, ApiCallBackHandler<XDocument> errorCalback, bool testmode=false)
        {
            startInfo = new StartInfo();
            HtmlDocument doc = HtmlPage.Document;
            if (doc.QueryString != null)
            {
                string tmp;
                bool isRequiredSet = true;
                isRequiredSet = doc.QueryString.TryGetValue("api_url", out startInfo.api_url) || isRequiredSet;
                isRequiredSet = doc.QueryString.TryGetValue("api_id", out startInfo.api_id) || isRequiredSet;
                isRequiredSet = doc.QueryString.TryGetValue("api_settings", out startInfo.api_settings) || isRequiredSet;
                doc.QueryString.TryGetValue("viewer_id", out startInfo.viewer_id);
                if (doc.QueryString.TryGetValue("viewer_type", out tmp))
                {
                    startInfo.viewer_type = int.Parse(tmp);
                }
                doc.QueryString.TryGetValue("user_id", out startInfo.user_id);
                doc.QueryString.TryGetValue("group_id", out startInfo.group_id);
                if (doc.QueryString.TryGetValue("is_app_user", out tmp))
                {
                    startInfo.is_app_user = int.Parse(tmp)==1;
                }
                doc.QueryString.TryGetValue("auth_key", out startInfo.auth_key);
                if (doc.QueryString.TryGetValue("language", out tmp))
                {
                    startInfo.language = int.Parse(tmp);
                }
                doc.QueryString.TryGetValue("secret", out startInfo.secret);
                isRequiredSet = doc.QueryString.TryGetValue("sid", out startInfo.sid) || isRequiredSet;
                if (testmode) startInfo.test_mode = true;

                // Если один из обязательных параметров не указан,
                // выбрасывается исключение
                if (!isRequiredSet)
                    throw new ArgumentNullException("Один из обязательных параметров не задан.", new NullReferenceException());

            }

            startInfo.server_secret = secret;

            dataProvider = new DataProvider(startInfo,errorCalback);
        }


        #region Result Handlers - принимают и обрабатывают результаты запроса, отправляя их затем в назначеную колбек функцию

        #region Обработчики функций работы с пользователями

        private void IsAppUserHendler(DataProvider.AsyncApiResponse result)
        {
            try
            {
                XDocument xmlDocument = result._xmlResponse;
                if (xmlDocument == null || xmlDocument.Root.Name.LocalName == "error")
                {
                    XDocument[] error = { xmlDocument };
                    result._dispatcher.BeginInvoke(result._errorCallBack, error);
                    return;
                }

                profile = new Profile();
                XElement userElement = xmlDocument.Element("response");
                bool response = userElement.Value == "1" ? true : false;
                if (result._dispatcher != null)
                {
                    bool[] pars = { response };
                    result._dispatcher.BeginInvoke(result._callBack, pars);
                }

                isProfileLoaded = true;
            }
            catch (Exception ex)
            {
                lastError = ex.Message;
            }
        }
        private void PhotoUriHandler(DataProvider.AsyncApiResponse result)
        {
            try
            {
                XDocument xmlDocument = result._xmlResponse;
                if (xmlDocument == null || xmlDocument.Root.Name.LocalName == "error")
                {
                    XDocument[] error = { xmlDocument };
                    result._dispatcher.BeginInvoke(result._errorCallBack, error);
                    return;
                }

                string value = xmlDocument.Element("response").Element("user").Element("photo_medium").Value;
                object[] pars = { value };
                result._dispatcher.BeginInvoke(result._callBack, pars);
            }
            catch (Exception ex)
            {
                lastError = ex.Message;
            }
        }
        private void LoadProfileHandler(DataProvider.AsyncApiResponse result)
        {
            try
            {
                XDocument xmlDocument = result._xmlResponse;
                if (xmlDocument == null || xmlDocument.Root.Name.LocalName == "error")
                {
                    XDocument[] error = { xmlDocument };
                    result._dispatcher.BeginInvoke(result._errorCallBack, error);
                    return;
                }

                profile = new Profile();
                XElement userElement = xmlDocument.Element("response").Element("user");
                profile.Id = userElement.Element("uid").Value;
                profile.FirstName = userElement.Element("first_name").Value;
                profile.LastName = userElement.Element("last_name").Value;
                profile.NickName = userElement.Element("nickname").Value;
                profile.Domain = userElement.Element("domain").Value;
                profile.Photo = userElement.Element("photo").Value;
                profile.Sex = Profile.ESex.Male;
                if (userElement.Element("sex").Value == "1")
                {
                    profile.Sex = Profile.ESex.Female;
                }
                if (result._dispatcher != null)
                {
                    Profile[] pars = { profile };
                    result._dispatcher.BeginInvoke(result._callBack, pars);
                }

                isProfileLoaded = true;
            }
            catch (Exception ex)
            {
                lastError = ex.Message;
            }
        }
        private void GetProfilesHandler(DataProvider.AsyncApiResponse result)
        {
            try
            {
                XDocument xmlDocument = result._xmlResponse;
                if (xmlDocument == null || xmlDocument.Root.Name.LocalName == "error")
                {
                    XDocument[] error = { xmlDocument };
                    result._dispatcher.BeginInvoke(result._errorCallBack, error);
                    return;
                }

                List<Profile> profiles = new List<Profile>();
                IEnumerable<XElement> XMLProfiles = xmlDocument.Element("response").Elements(xmlDocument.Element("response").Element("user").Name);

                foreach (XElement userElement in XMLProfiles)
                {
                    profile = new Profile();
                    profile.Id = userElement.Element("uid").Value;
                    profile.FirstName = userElement.Element("first_name").Value;
                    profile.LastName = userElement.Element("last_name").Value;
                    profile.NickName = userElement.Element("nickname").Value;
                    profile.Domain = userElement.Element("domain").Value;
                    profile.Photo = userElement.Element("photo").Value;
                    profile.BirthDate = userElement.Element("bdate").Value;
                    profile.City = userElement.Element("city").Value;
                    profile.Country = userElement.Element("country").Value;
                    profile.Timezone = userElement.Element("timezone").Value;
                    profile.PhotoMedium = userElement.Element("photo_medium").Value;
                    profile.PhotoBig = userElement.Element("photo_big").Value;
                    profile.HasMobile = userElement.Element("has_mobile").Value == "1"? true:false;
                    profile.Rate = userElement.Element("rate").Value;
                    profile.Contacts = userElement.Element("contacts").Value;
                    profile.Education = userElement.Element("education").Value;
                    profile.Sex = Profile.ESex.Male;
                    if (userElement.Element("sex").Value == "1")
                    {
                        profile.Sex = Profile.ESex.Female;
                    }
                    profiles.Add(profile);
                }
                
                
                if (result._dispatcher != null)
                {
                    List<Profile>[] pars = { profiles };
                    result._dispatcher.BeginInvoke(result._callBack, pars);
                }

                isProfileLoaded = true;
            }
            catch (Exception ex)
            {
                lastError = ex.Message;
            }
        }
        private void GetUserBalanceHandler(DataProvider.AsyncApiResponse result)
        {
            try
            {
                XDocument xmlDocument = result._xmlResponse;
                if (xmlDocument == null || xmlDocument.Root.Name.LocalName == "error")
                {
                    XDocument[] error = { xmlDocument };
                    result._dispatcher.BeginInvoke(result._errorCallBack, error);
                    return;
                }

                profile = new Profile();
                XElement userElement = xmlDocument.Element("response").Element("balance");
                decimal response = decimal.Parse(userElement.Value);
                if (result._dispatcher != null)
                {
                    decimal[] pars = { response };
                    result._dispatcher.BeginInvoke(result._callBack, pars);
                }

                isProfileLoaded = true;
            }
            catch (Exception ex)
            {
                lastError = ex.Message;
            }
        }
        #endregion

        private void PutVarHandler(DataProvider.AsyncApiResponse result)
        {
            try
            {
                XDocument xmlDocument = result._xmlResponse;
                bool retCode = false;
                if (xmlDocument == null || xmlDocument.Root.Name.LocalName == "error")
                {
                    XDocument[] error = { xmlDocument };
                    result._dispatcher.BeginInvoke(result._errorCallBack, error);
                    return;
                }
                
                retCode = true;

                if (result._dispatcher != null)
                {
                    bool[] pars = { retCode };
                    result._dispatcher.BeginInvoke(result._callBack, pars);
                }
            }
            catch (Exception ex)
            {
                lastError = ex.Message;
            }
        }
        private void GetVarsHandler(DataProvider.AsyncApiResponse result)
        {
            try
            {
                XDocument xmlDocument = result._xmlResponse;
                if (xmlDocument == null || xmlDocument.Root.Name.LocalName == "error")
                {
                    XDocument[] error = { xmlDocument };
                    result._dispatcher.BeginInvoke(result._errorCallBack, error);
                    return;
                }
                Dictionary<int, string> response = new Dictionary<int, string>();
                IEnumerable<XElement> varElements = xmlDocument.Element("response").Elements("var_info");
                foreach (XElement varElement in varElements)
                {
                    int key = Convert.ToInt32(varElement.Element("key").Value);
                    string value = varElement.Element("value").Value;
                    response.Add(key, value);
                }

                if (result._dispatcher != null)
                {
                    Dictionary<int, string>[] pars = { response };
                    result._dispatcher.BeginInvoke(result._callBack, pars);
                }
            }
            catch (Exception ex)
            {
                lastError = ex.Message;
            }
        }
        private void GetHighScoresHandler(DataProvider.AsyncApiResponse result)
        {
            try
            {
                XDocument xmlDocument = result._xmlResponse;
                if (xmlDocument == null || xmlDocument.Root.Name.LocalName == "error")
                {
                    XDocument[] error = { xmlDocument };
                    result._dispatcher.BeginInvoke(result._errorCallBack, error);
                    return;
                }
                List<Record> response = new List<Record>();
                IEnumerable<XElement> scoreElements = xmlDocument.Element("response").Elements("score_info");
                foreach (XElement scoreElement in scoreElements)
                {
                    string Id = scoreElement.Element("user_id").Value;
                    string Name = scoreElement.Element("user_name").Value;
                    int Value = Convert.ToInt32(scoreElement.Element("score").Value);
                    response.Add(new Record(Id, Name, Value));
                }

                if (result._dispatcher != null)
                {
                    List<Record>[] pars = { response };
                    result._dispatcher.BeginInvoke(result._callBack, pars);
                }
            }
            catch (Exception ex)
            {
                lastError = ex.Message;
            }
        }
        #endregion Handlers


        #region Реализация интерфейса социализации ISocial

        /// <summary>
        /// Возвращает идентификатор пользователя приложения
        /// </summary>
        /// <returns>ID пользователя</returns>
        public string GetId()
        {
            return startInfo.viewer_id;
        }
        /// <summary>
        /// Возвращает установил пользователь данное приложение или нет.
        /// Функция берет данные из глобальных настроек приложения
        /// </summary>
        /// <returns>True - является, false - не является</returns>
        public bool IsAppUser()
        {
            return startInfo.is_app_user;
        }
        /// <summary>
        /// Загрузка профиля текущего пользователя
        /// </summary>
        /// <param name="dispatcher">Диспетчер, который будет ловить события (обычно это this.Dispatcher)</param>
        /// <param name="callBack">Колбек фнкция, которая будет принимать результат выполнения запроса</param>
        /// <param name="errorCallBack">Функция принимающая сообщение об ошибке</param>
        public void LoadProfile(Dispatcher dispatcher = null, ApiCallBackHandler<Profile> callBack = null, ApiCallBackHandler<XDocument> errorCallBack = null)
        {
            DataProvider.AsyncApiResponse data = new DataProvider.AsyncApiResponse(LoadProfileHandler, dispatcher, callBack, errorCallBack);
            Dictionary<string, string> parameters = new Dictionary<string, string>();
            parameters.Add("uids", startInfo.viewer_id);
            parameters.Add("fields", "uid,first_name,last_name,nickname,domain,sex,photo");
            dataProvider.Request("getProfiles", data, parameters);
        }
        /// <summary>
        /// Загружен ли профиль
        /// </summary>
        /// <returns>true - загружен, иначе false</returns>
        public bool IsProfileLoaded()
        {
            return isProfileLoaded;
        }
        /// <summary>
        /// Получить загруженный профиль пользователя приложения
        /// </summary>
        /// <returns>Профиль пользователя</returns>
        public Profile GetProfile()
        {
            return profile;
        }
        /// <summary>
        /// Получить ссылку на фото пользователя
        /// </summary>
        /// <param name="id">Идентификатор пользоватеял</param>
        /// <param name="dispatcher">Диспетчер, который будет ловить события (обычно это this.Dispatcher)</param>
        /// <param name="callBack">Колбек фнкция, которая будет принимать результат выполнения запроса</param>
        /// <param name="errorCallBack">Функция принимающая сообщение об ошибке</param>
        public void GetPhotoUri(string id, Dispatcher dispatcher, ApiCallBackHandler<string> callBack, ApiCallBackHandler<XDocument> errorCallBack = null)
        {
            DataProvider.AsyncApiResponse data = new DataProvider.AsyncApiResponse(PhotoUriHandler, dispatcher, callBack, errorCallBack);
            Dictionary<string, string> parameters = new Dictionary<string, string>();
            parameters.Add("uids", id);
            parameters.Add("fields", "photo_medium");
            dataProvider.Request("getProfiles", data, parameters);
        }
        /// <summary>
        /// Установить глобальную переменную в хранилище ВКонтакте
        /// </summary>
        /// <param name="key">Номер ячейки переменной (от 0 до 923)</param>
        /// <param name="value">Значение, которое нужно записать в переменную. Строка в utf-8, не более 255 байтов</param>
        /// <param name="dispatcher">Диспетчер, который будет ловить события (обычно это this.Dispatcher)</param>
        /// <param name="callBack">Колбек фнкция, которая будет принимать результат выполнения запроса</param>
        /// <param name="errorCallBack">Функция принимающая сообщение об ошибке</param>
        public void PutGlobalVar(int key, string value, Dispatcher dispatcher = null, ApiCallBackHandler<bool> callBack = null, ApiCallBackHandler<XDocument> errorCallBack = null)
        {
            // 100 - 1023
            putVariable(key + 100, value, dispatcher, callBack,errorCallBack);
        }
        /// <summary>
        /// Получить глобальную переменную из хранилища ВКонтакте. Данный метод позволяет получить за один запрос до 32 переменных
        /// </summary>
        /// <param name="key">Номер ячейки переменной (от 0 до 923)</param>        
        /// <param name="count">Количество переменных, которые нужно вытащить из хранилища ВКонтакте.</param>
        /// <param name="dispatcher">Диспетчер, который будет ловить события (обычно это this.Dispatcher)</param>
        /// <param name="callBack">Колбек фнкция, которая будет принимать результат выполнения запроса</param>
        /// <param name="errorCallBack">Функция принимающая сообщение об ошибке</param>
        public void GetGlobalVars(int key, Dispatcher dispatcher, int count = 1, ApiCallBackHandler<Dictionary<int, string>> callBack = null, ApiCallBackHandler<XDocument> errorCallBack = null)
        {
            // 100 - 1023
            getVariables(key + 100, dispatcher, callBack, count, errorCallBack);
        }

        /// <summary>
        /// Установить пользовательскую переменную в хранилище ВКонтакте
        /// </summary>
        /// <param name="key">Номер ячейки переменной (от 0 до 247)</param>
        /// <param name="value">Значение, которое нужно записать в переменную. Строка в utf-8, не более 255 байтов</param>
        /// <param name="dispatcher">Диспетчер, который будет ловить события (обычно это this.Dispatcher)</param>
        /// <param name="callBack">Колбек фнкция, которая будет принимать результат выполнения запроса</param>
        /// <param name="errorCallBack">Функция принимающая сообщение об ошибке</param>
        public void PutUserVar(int key, string value, Dispatcher dispatcher = null, ApiCallBackHandler<bool> callBack = null, ApiCallBackHandler<XDocument> errorCallBack = null)
        {
            // 1800 - 2047
            putVariable(key + 1800, value, dispatcher, callBack,errorCallBack);
        }
        /// <summary>
        /// Получить пользовательскую переменную из хранилища ВКонтакте. Данный метод позволяет получить за один запрос до 32 переменных
        /// </summary>
        /// <param name="key">Номер ячейки переменной (от 0 до 247)</param>        
        /// <param name="count">Количество переменных, которые нужно вытащить из хранилища ВКонтакте.</param>
        /// <param name="dispatcher">Диспетчер, который будет ловить события (обычно это this.Dispatcher)</param>
        /// <param name="callBack">Колбек фнкция, которая будет принимать результат выполнения запроса</param>
        /// <param name="errorCallBack">Функция принимающая сообщение об ошибке</param>
        public void GetUserVars(int key, Dispatcher dispatcher, ApiCallBackHandler<Dictionary<int, string>> callBack, int count = 1, ApiCallBackHandler<XDocument> errorCallBack = null)
        {
            // 1800 - 2047
            getVariables(key + 1800, dispatcher, callBack, count,errorCallBack);
        }
        /// <summary>
        /// Показать окно "Пригласить друга"
        /// </summary>
        public void ShowInviteFriendBox()
        {
            HtmlPage.Window.Invoke("vk_init", null);
            HtmlPage.Window.Invoke("ShowInviteBox", null);
        }

        #endregion
    }
}
